package utils

import (
	"container/list"
	"sync"
	"time"
)

type Item struct {
	t time.Time // 过期时间
	k any       // 键
	v any       // 值
}

type KVPair struct {
	K any // 键
	V any // 值
}

// OrderTimeoutMap 存储着具有固定超时时间的对象
// 是一个map和链表的结合，map赋予了根据key查询value的能力，链表时value有序排列
// 向它Put已有的key相当于刷新过期时间
type OrderTimeoutMap struct {
	m        sync.Map
	linklist *list.List
	alive    time.Duration
	mux      sync.RWMutex
}

func NewOrderTimeoutMap(alive time.Duration) *OrderTimeoutMap {
	result := OrderTimeoutMap{
		m:        sync.Map{},
		linklist: list.New(),
		alive:    alive,
		mux:      sync.RWMutex{},
	}
	return &result
}

func (otm *OrderTimeoutMap) GetAliveTime() time.Duration {
	return otm.alive
}

// Put 向它Put已有的key相当于刷新过期时间
func (otm *OrderTimeoutMap) Put(key, value any) {
	otm.mux.Lock()
	defer otm.mux.Unlock()
	v, ok := otm.m.Load(key)
	if !ok {
		e := otm.linklist.PushBack(&Item{
			k: key,
			v: value,
			t: time.Now().UTC().Add(otm.alive),
		})
		otm.m.Store(key, e)
	} else {
		otm.linklist.Remove(v.(*list.Element))
		e := otm.linklist.PushBack(&Item{
			k: key,
			v: value,
			t: time.Now().UTC().Add(otm.alive),
		})
		otm.m.Store(key, e)
	}
}

// PutBatch 批量导入
func (otm *OrderTimeoutMap) PutBatch(kvs []KVPair) {
	otm.mux.Lock()
	defer otm.mux.Unlock()
	for _, kv := range kvs {
		v, ok := otm.m.Load(kv.K)
		if !ok {
			e := otm.linklist.PushBack(&Item{
				k: kv.K,
				v: kv.V,
				t: time.Now().UTC().Add(otm.alive),
			})
			otm.m.Store(kv.K, e)
		} else {
			otm.linklist.Remove(v.(*list.Element))
			e := otm.linklist.PushBack(&Item{
				k: kv.K,
				v: kv.V,
				t: time.Now().UTC().Add(otm.alive),
			})
			otm.m.Store(kv.K, e)
		}
	}
}

func (otm *OrderTimeoutMap) Get(key any, evenTimeOut bool) (data any, isTimeOut bool) {
	rl := otm.mux.RLocker()
	rl.Lock()
	value, ok := otm.m.Load(key)
	if !ok || value == nil {
		rl.Unlock()
		return nil, false
	}
	ele := value.(*list.Element)
	item := ele.Value.(*Item)
	if !item.t.After(time.Now().UTC()) {
		// 超时，需要删除
		rl.Unlock()
		otm.mux.Lock()
		otm.m.Delete(key)
		otm.linklist.Remove(ele)
		otm.mux.Unlock()
		if evenTimeOut {
			return item.v, true
		}
		return nil, true
	} else {
		rl.Unlock()
		return item.v, false
	}
}

func (otm *OrderTimeoutMap) GetAndDelete(key any, evenTimeOut bool) (data any, isTimeOut bool) {
	otm.mux.Lock()
	defer otm.mux.Unlock()
	value, load := otm.m.LoadAndDelete(key)
	if !load {
		return nil, false
	}
	ele := value.(*list.Element)
	item := ele.Value.(*Item)
	otm.linklist.Remove(ele)
	if !item.t.After(time.Now().UTC()) {
		// 超时
		isTimeOut = true
		if evenTimeOut {
			data = item.v
			return
		} else {
			data = nil
			return
		}
	} else {
		data = item.v
		isTimeOut = false
		return
	}
}

func (otm *OrderTimeoutMap) Delete(key any) {
	otm.mux.Lock()
	defer otm.mux.Unlock()
	value, load := otm.m.LoadAndDelete(key)
	if !load {
		return
	}
	ele := value.(*list.Element)
	otm.linklist.Remove(ele)
}

// CleanTimeout 将过期清除，并返回过期的值
func (otm *OrderTimeoutMap) CleanTimeout() []KVPair {
	otm.mux.Lock()
	defer otm.mux.Unlock()
	index := otm.linklist.Front()
	var next *list.Element = nil
	result := make([]KVPair, 0)
	l := otm.linklist.Len()
	for i := 0; i < l; i++ {
		item := index.Value.(*Item)
		if !item.t.After(time.Now().UTC()) {
			// 过期了
			next = index.Next()
			otm.m.Delete(item.k)
			result = append(result, KVPair{
				K: item.k,
				V: item.v,
			})
			otm.linklist.Remove(index)
			if next == nil {
				break
			} else {
				index = next
				next = nil
			}
		} else {
			break
		}
	}
	return result
}

// Size 返回map的容量，包含已经过期的键值对
func (otm *OrderTimeoutMap) Size() int {
	rl := otm.mux.RLocker()
	rl.Lock()
	defer rl.Unlock()
	return otm.linklist.Len()
}

// List 列出所有元素
func (otm *OrderTimeoutMap) List(evenTimeout bool) []any {
	result := make([]any, 0)
	rl := otm.mux.RLocker()
	rl.Lock()
	defer rl.Unlock()
	l := otm.linklist.Len()
	index := otm.linklist.Front()
	now := time.Now().UTC()
	for i := 0; i < l; i++ {
		item := index.Value.(*Item)
		if evenTimeout {
			result = append(result, item.v)
		} else {
			if !item.t.Before(now) {
				result = append(result, item.v)
			} else {
				break
			}
		}
		index = index.Next()
		if index == nil {
			break
		}
	}
	return result
}
